﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using cmdLineParserModule.HelperClasses;

namespace cmdLineParserModule.HelperClasses
{
    /// <summary>
    /// Works with arguments as tokens, finds arguments as choices, parameters or ordinary parameters
    /// </summary>
    internal class TokenManager
    {
        /// <summary>
        /// Token list
        /// </summary>
        private List<cmdLineToken> tokens = null;


        /// <summary>
        /// Gets arguments, that are not choices nor choice parameters
        /// </summary>
        /// <returns>Ordinary arguments</returns>
        public string[] ordinaryParameters()
        {
            return (
                    from token in tokens
                    where !token.isUsed
                    select token.originalText
                   ).ToArray();
        }

        /// <summary>
        /// Checks if there were some unknown choices (unused = unknown in this case)
        /// </summary>
        public void checkPendingChoices()
        {
            // searches for unused choices
            var unusedChoices = from token in tokens
                                where token.isChoice && !token.isUsed
                                select token;

            if (unusedChoices.Count() > 0)
            { throw new WrongUsageException("Unknown choice: {0}", unusedChoices.First().unprefixedText); }
        }

        /// <summary>
        /// Finds token representing given choice
        /// </summary>
        /// <param name="command">command that is searched</param>
        /// <param name="alternatives">alternative commands that also fit given choice</param>
        /// <param name="choiceToken">Token containing choice (or null when not found)</param>
        /// <param name="paramToken">Token containing parameter (or null when not found)</param>
        public void findChoiceAndParamToken(string command, string[] alternatives, out cmdLineToken choiceToken, out cmdLineToken paramToken)
        {
            IEnumerable<cmdLineToken> TokenList = from s in tokens
                                                  where (s.isChoice && (s.unprefixedText.Equals(command) || (alternatives != null && alternatives.Contains(s.unprefixedText))))
                                                  select s;

            // if choiceToken not found/found/found too many times
            switch (TokenList.Count())
            {
                case 0:
                    choiceToken = null;
                    paramToken = null;
                    break;
                case 1:
                    // here we have choice token
                    choiceToken = TokenList.Single();
                    int i = tokens.IndexOf(choiceToken);

                    // try to find parameter for choice
                    if (i < tokens.Count() - 1)
                    {
                        if (!tokens[i + 1].isUsed)
                            paramToken = tokens[i + 1];
                        else
                            paramToken = null;
                    }
                    else
                    {
                        paramToken = null;
                    }

                    break;
                default: throw new WrongUsageException("Multiple use of choice {0}", TokenList.First().unprefixedText);
            }
        }

        /// <summary>
        /// Adds use of command and alternatives
        /// </summary>
        /// <param name="attr"></param>
        public void addUseOf(optionAttribute attr)
        {
            addToUsedCommands(attr.command);
            foreach (string cmd in attr.alternatives)
            {
                addToUsedCommands(cmd);
            }
        }

        /// <summary>
        /// Creates token managers and inits token list according to command line arguments
        /// </summary>
        /// <param name="args">Command line arguments</param>
        public TokenManager(string[] args)
        {
            this.tokens = createTokenList(args);
            usedCommands = new List<string>();
        }


        /// <summary>
        /// Commands already used, helps to determine duplication use of choice
        /// </summary>
        private List<string> usedCommands = null;

        /// <summary>
        /// Adds command to list of used commands
        /// </summary>
        /// <param name="s"></param>
        private void addToUsedCommands(string s)
        {
            if (usedCommands.Contains(s))
            {
                throw new WrongUsageException("Choice {0} already used.", s);
            }
            else
            {
                usedCommands.Add(s);
            }
        }
        
        /// <summary>
        /// Creates list of tokens from string array
        /// </summary>
        /// <param name="commands">List of commands</param>
        /// <returns>List of tokens</returns>
        private List<cmdLineToken> createTokenList(string[] commands)
        {
            bool separatorFound = false;
            List<cmdLineToken> tokens = new List<cmdLineToken>();

            foreach (string command in commands)
            {
                // creates new token (that recognizes what it is, choice, parameter or nothing)
                cmdLineToken newToken = new cmdLineToken(separatorFound, command);

                tokens.Add(newToken);

                // when separator is found, we must act
                if (command.Equals(ParserConsts.ARGUMENT_SEPARATOR))
                {
                    separatorFound = true;
                    newToken.isChoice = false;
                    newToken.isUsed = true;
                }
            }

            return tokens;
        }
    }
}
